package com.aos.iot.socket.common.annotation;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class TcpEventListenerHub {

    public static final String CONNECT = "connect";

    public static final String DISCONNECT = "disconnect";

    /**
     * Protocol -> cmd -> listeners -> Map<method, bean>
     */
    private final static Map<String, Map<String, List<Map<Method, Object>>>> cmdEventEntry = new ConcurrentHashMap<>();

    public static void addConnectListener(OnConnect onConnect, Object bean, Method method) {
        addListener(onConnect.protocol(), new String[]{CONNECT}, bean, method);
    }

    public static void addDisconnectListener(OnDisconnect onDisconnect, Object bean, Method method) {
        addListener(onDisconnect.protocol(), new String[]{DISCONNECT}, bean, method);
    }

    public static void addCmdEventListener(OnCmdEvent onCmdEvent, Object bean, Method method) {
        addListener(onCmdEvent.protocol(), onCmdEvent.cmd(), bean, method);
    }

    public static void addListener(Object bean, Method method) {
        if(method.isAnnotationPresent(OnConnect.class)) {
            addConnectListener(method.getAnnotation(OnConnect.class), bean, method);
        } else if(method.isAnnotationPresent(OnDisconnect.class)) {
            addDisconnectListener(method.getAnnotation(OnDisconnect.class), bean, method);
        } else if (method.isAnnotationPresent(OnCmdEvent.class)) {
            addCmdEventListener(method.getAnnotation(OnCmdEvent.class), bean, method);
        }
    }

    public static void addListener(String protocol, String[] cmds, Object bean, Method method) {
        if(!cmdEventEntry.containsKey(protocol)) {
            cmdEventEntry.put(protocol, new ConcurrentHashMap<>());
        }
        for (String cmd : cmds) {
            Map<String, List<Map<Method, Object>>> protocolEntry = cmdEventEntry.get(protocol);
            if(!protocolEntry.containsKey(cmd)) {
                protocolEntry.put(cmd, new ArrayList<>());
            }
            List<Map<Method, Object>> cmdListeners = protocolEntry.get(cmd);
            Map<Method, Object> listener = new HashMap<>();
            listener.put(method, bean);
            cmdListeners.add(listener);
        }
    }

    public static void invoke(String protocol, String cmd, Object... args) {
        ThreadUtil.execute(() -> {
            try {
                if(cmdEventEntry.containsKey(protocol)) {
                    Map<String, List<Map<Method, Object>>> protocolEntry = cmdEventEntry.get(protocol);
                    if(protocolEntry.containsKey(cmd)) {
                        List<Map<Method, Object>> cmdListeners = protocolEntry.get(cmd);
                        for(Map<Method, Object> listener : cmdListeners) {
                            listener.forEach((m, b) -> {
                                try {
                                    Object bean = ObjectUtil.defaultIfNull(SpringUtil.getBean(b.getClass()), b);
                                    m.invoke(bean, transform(m, args));
                                } catch (Exception ignored) {}
                            });
                        }
                    }
                }
            } catch (Exception e) {
                log.error("protocol["+protocol+"] cmd["+cmd+"]", e);
            }
        });
    }

    public static Object[] transform(Method method, Object... args) {
        List<Object> argsList = new ArrayList<>();
        for (Class<?> parameterType : method.getParameterTypes()) {
            Object param = null;
            for (Object arg : args) {
                if(parameterType.isInstance(arg)) {
                    param = arg;
                }
            }
            argsList.add(param);
        }
        return argsList.toArray();
    }
}
